﻿#region License statement
// NJamb, a specification and delta-specification DSL
// Copyright (c) 2010-2011, Mark Knell
// Published under the MIT License; all other rights reserved
#endregion

#region using...
using System;
using System.Collections.Generic;
using System.Linq.Expressions;
using NJamb.Parser.SubjectBuilders;
using NJamb.Parser.Subjects.Enumerable;
using NJamb.Parser.Subjects.Singular;
using NJamb.Specifications.Delta;
using NUnit.Framework;
#endregion

namespace NJamb.NUnit
{
	[TestFixture]
	public abstract class FixtureBase<TSubject>
		where TSubject : class
	{
		protected State _state;

		[SetUp]
		public void Init()
		{
			_state = new State(TestSubjectFactory());
		}

		[TearDown]
		public void TearDown()
		{
			RunExperiment();
			Assert.That(_state.Experiment.HasRun);
		}

		protected IExperiment<TSubject> Experiment
		{
			get { return _state.Experiment; }
		}
		protected TSubject TestSubject
		{
			get { return _state.TestSubject; }
			set { _state.TestSubject = value; }
		}

		protected void RunExperiment()
		{
			Assert.That(_state.Experiment, Is.Not.Null, "Experiment cannot be null.");
			Assert.That(_state.HasAction, "Action on the test subject cannot be null.");
			var collaboratingExperiment = (ICollaboratingExperiment<TSubject>) _state.Experiment;
			Assert.That(_state.TestSubject,
				Is.SameAs(collaboratingExperiment.Subject),
				"Error: TestSubject is not the Subject of the experiment; possible lambda error?");
			if (!Experiment.HasRun)
			{
				_state.Try();
			}
		}

		protected abstract TSubject TestSubjectFactory();

		protected ISingularSubject<TValue> That<TValue>(Expression<Func<TSubject, TValue>> expression)
		{
			return Specify.For(() => TestSubject).That(expression);
		}

		protected IEnumerableSubject<ICollection<TItem>, TItem> ThatTheCollection<TItem>(
			Expression<Func<TSubject, ICollection<TItem>>> expression)
		{
			return Specify.For(() => TestSubject).ThatTheCollection(expression);
		}

		protected IEnumerableSubject<IEnumerable<TItem>, TItem> ThatTheSequence<TItem>(
			Expression<Func<TSubject, IEnumerable<TItem>>> expression)
		{
			return Specify.For(() => TestSubject).ThatTheSequence(expression);
		}

		protected class State
		{
			private readonly Action<string> _callbackOnSuccess;
			private readonly Lazy<Experiment.Builder<TSubject>> _lazyExperimentBuilder;
			private readonly List<Action> _postconditions;

			public State(TSubject subject, Action<string> callbackOnSuccess = null)
			{
				TestSubject = subject;
				_callbackOnSuccess = callbackOnSuccess;
				_lazyExperimentBuilder = new Lazy<Experiment.Builder<TSubject>>(() => NJamb.Experiment.On(TestSubject));
				_postconditions = new List<Action>();
			}

			public Action<TSubject> Action { get; set; }
			public IExperiment<TSubject> Experiment { get; private set; }

			public Experiment.Builder<TSubject> ExperimentBuilder
			{
				get { return _lazyExperimentBuilder.Value; }
			}
			public bool HasAction
			{
				get { return Action != null; }
			}
			public TSubject TestSubject { get; set; }

			public void AndVerify(Action assert, params Action[] additional)
			{
				_postconditions.Add(assert);
				_postconditions.AddRange(additional);
			}

			public void Expecting(IDeltaSpecification deltaSpecification, params IDeltaSpecification[] additional)
			{
				Experiment = ExperimentBuilder.Expecting(deltaSpecification, additional);
			}

			public void Try()
			{
				if (Action == null)
				{
					throw new InvalidOperationException("Must have an action defined.");
				}
				Experiment.Try(Action, Assert.Fail, _callbackOnSuccess);
				foreach (Action assertion in _postconditions)
				{
					assertion.Invoke();
				}
			}
		}
	}
}
